////////////////////////////////////////////////////////////
// Headers
////////////////////////////////////////////////////////////
#include "JumpPointSearch.hpp"

#include <SmegTools/Tools.hpp>
#include <SmegCore/Math/Math.hpp>

#include "../Grid.hpp"

#include <SmegTools/Debug/DebugNew.hpp>

namespace smeg {
    namespace ai {
        
////////////////////////////////////////////////////////////
/// Default Constructor
////////////////////////////////////////////////////////////
JumpPointSearch::JumpPointSearch() :
    BasePathfindingAlgorithm()
{
}

////////////////////////////////////////////////////////////
/// Constructor
////////////////////////////////////////////////////////////
JumpPointSearch::JumpPointSearch( Grid* _pGrid ) :
    BasePathfindingAlgorithm( _pGrid )
{
}


////////////////////////////////////////////////////////////
/// Destructor
////////////////////////////////////////////////////////////
JumpPointSearch::~JumpPointSearch()
{
}

////////////////////////////////////////////////////////////
/// Find a path between start and end
////////////////////////////////////////////////////////////
const std::vector< sf::Vector2f >& JumpPointSearch::FindPath( const sf::Vector2f& _Start, const sf::Vector2f& _End ) {
    s_PoolNode.ReleaseAll();
    ClearPath();

    sf::Vector2i startPoint,
        endPoint,
        tmpPoint;
    Node* pCurrentNode,
        * pNeighbourNode,
        * pStartNode = s_PoolNode.Next(),
        * pEndNode = s_PoolNode.Next();
    std::vector< Node* > successors;
    unsigned uNumSuccessors,
        uNumCells = m_pGrid->GetNumCells();
    int iMapIdx;
    
    m_pGrid->ConvertToGridSpace( (int)_Start.x, (int)_Start.y, startPoint );
    m_pGrid->ConvertToGridSpace( (int)_End.x, (int)_End.y, endPoint );
    
    pStartNode->Setup( startPoint.x, startPoint.y );
    pEndNode->Setup( endPoint.x, endPoint.y );

    if ( m_pGrid->IsBlocked( endPoint.x, endPoint.y ) ) {
        return m_LastFoundPath;
    }

    pStartNode->F = Heuristic( startPoint, endPoint );

    m_DoneNodes.insert( td_PathMap::value_type( Node::UniqueKey( pStartNode, uNumCells ), pStartNode ) );
    m_OpenSet.push_back( pStartNode );

    td_PathPriorityQueue::iterator itLess,
        it;
    while ( !m_OpenSet.empty() ) {
        itLess = m_OpenSet.begin();
        for ( it = itLess + 1; it != m_OpenSet.end(); ++it ) {
            if ( **it < **itLess ) {
                itLess = it;
            }
        }
        pCurrentNode = *itLess;
        m_OpenSet.erase( itLess );

        // If this is the end node, reconstruct the path
        if ( (*pCurrentNode)  == (*pEndNode) ) {
            ReconstructPath( pCurrentNode );
            return m_LastFoundPath;
        }

        m_ClosedMap.insert( td_PathMap::value_type( Node::UniqueKey( pCurrentNode, uNumCells ), pCurrentNode ) );

        // Find the successors
        successors.clear();
        FindSuccessors( pCurrentNode, pStartNode, pEndNode, successors );
        uNumSuccessors = successors.size();
        for ( unsigned i = 0; i < uNumSuccessors; ++i ) {
            pNeighbourNode  = successors[i];
            tmpPoint.x      = pNeighbourNode->X;
            tmpPoint.y      = pNeighbourNode->Y;
            iMapIdx         = Node::UniqueKey( pNeighbourNode, uNumCells );

            // If successor is in the closed map, continue
            if ( m_ClosedMap.count( iMapIdx ) != 0 ) {
                s_PoolNode.Release( pNeighbourNode );
                continue;
            }

            // Setup the neighbour
            pNeighbourNode->parent  = pCurrentNode;
            pNeighbourNode->G       = pCurrentNode->G + pNeighbourNode->DistanceTo( *pCurrentNode );

            // If this is the end node, reconstruct the path
            if ( (*pNeighbourNode) == (*pEndNode) ) {
                ReconstructPath( pNeighbourNode );
                return m_LastFoundPath;
            }

            // If this node has already been computed, reprioritize it, don't push it
            td_PathMap::iterator itNeighbour = m_DoneNodes.find( iMapIdx );

            if ( itNeighbour == m_DoneNodes.end() ) {
                pNeighbourNode->F = pNeighbourNode->G + Heuristic( tmpPoint, endPoint );

                // Add it to the open set
                m_DoneNodes.insert( td_PathMap::value_type( iMapIdx, pNeighbourNode ) );
                m_OpenSet.push_back( pNeighbourNode );
            } else if ( pNeighbourNode->G < itNeighbour->second->G ) {
                // Reprioritize existing node
                itNeighbour->second->parent = pNeighbourNode->parent;
                itNeighbour->second->G      = pNeighbourNode->G;
                itNeighbour->second->F      = pNeighbourNode->G + Heuristic( tmpPoint, endPoint );

                s_PoolNode.Release( pNeighbourNode );
            } else {
                s_PoolNode.Release( pNeighbourNode );
            }
        }
    }

    return m_LastFoundPath;
}

////////////////////////////////////////////////////////////
/// Find a node to jump from the given one in the given direction
////////////////////////////////////////////////////////////
Node* JumpPointSearch::Jump( int _iNodeX, int _iNodeY,
                                    int _iDirX, int _iDirY,
                                    Node* _pStartNode, Node* _pEndNode ) {
    // Next node position
    int iNextX = _iNodeX + _iDirX,
        iNextY = _iNodeY + _iDirY;
    Node* pJumpNode;

    // If next node is blocked, we can't jump
    if ( m_pGrid->IsBlocked( iNextX, iNextY ) ) {
        return NULL;
    }

    // If next node is the end, return it
    if ( iNextX == _pEndNode->X && iNextY == _pEndNode->Y ) {
        pJumpNode = s_PoolNode.Next();
        pJumpNode->Setup( iNextX, iNextY );

        return pJumpNode;
    }

    int iOffsetX = iNextX,
        iOffsetY = iNextY;

    if ( _iDirX != 0 && _iDirY != 0 ) {
        // If -_iDirX node is blocked and -_iDirX/+_iDirY isn't, this is a jump node
        // If -_iDirY node is blocked and +_iDirX/-_iDirY isn't, this is a jump node
        if ( ( !m_pGrid->IsBlocked( iOffsetX - _iDirX, iOffsetY + _iDirY ) && 
            !m_pGrid->IsBlocked( iOffsetX, iOffsetY + _iDirY ) && 
            m_pGrid->IsBlocked( iOffsetX - _iDirX, iOffsetY ) ) ||
            ( !m_pGrid->IsBlocked( iOffsetX + _iDirX, iOffsetY - _iDirY ) &&  
            !m_pGrid->IsBlocked( iOffsetX + _iDirX, iOffsetY ) && 
            m_pGrid->IsBlocked( iOffsetX, iOffsetY - _iDirY ) ) ) {
            pJumpNode = s_PoolNode.Next();
            pJumpNode->Setup( iOffsetX, iOffsetY );

            return pJumpNode;
        }

        // If we can jump in _iDirX or in _iDirY of this node, this is a jump node
        if ( Jump( iOffsetX, iOffsetY, _iDirX, 0, _pStartNode, _pEndNode ) ||
            Jump( iOffsetX, iOffsetY, 0, _iDirY, _pStartNode, _pEndNode ) ) {
            pJumpNode = s_PoolNode.Next();
            pJumpNode->Setup( iOffsetX, iOffsetY );

            return pJumpNode;
        }

        // Next node in same X/Y direction
        iOffsetX += _iDirX;
        iOffsetY += _iDirY;
        
        // If next node is blocked, we can't jump
        if ( m_pGrid->IsBlocked( iOffsetX, iOffsetY ) ||
            ( m_pGrid->IsBlocked( iOffsetX - _iDirX, iOffsetY ) &&
            m_pGrid->IsBlocked( iOffsetX, iOffsetY - _iDirY ) )) {
            return NULL;
        }

        // If next node is the end, return it
        if ( iOffsetX == _pEndNode->X && iOffsetY == _pEndNode->Y ) {
            pJumpNode = s_PoolNode.Next();
            pJumpNode->Setup( iOffsetX, iOffsetY );

            return pJumpNode;
        }
    } else if ( _iDirX != 0 ) {
        while (true) {
            // If next node is blocked, we can't jump
            if ( m_pGrid->IsBlocked( iOffsetX + _iDirX, iNextY ) ) {
                return NULL;
            }

            // If up/down node is blocked and _iDirX:up/_iDirX:down isn't, this is a jump node
            if ( ( !m_pGrid->IsBlocked( iOffsetX + _iDirX, iNextY + 1 ) && m_pGrid->IsBlocked( iOffsetX, iNextY + 1 ) ) ||
                ( !m_pGrid->IsBlocked( iOffsetX + _iDirX, iNextY - 1 ) && m_pGrid->IsBlocked( iOffsetX, iNextY - 1 ) ) ) {
                pJumpNode = s_PoolNode.Next();
                pJumpNode->Setup( iOffsetX, iNextY );

                return pJumpNode;
            }

            // Next node in X direction
            iOffsetX += _iDirX;

            // If next node is the end, return it
            if ( iOffsetX == _pEndNode->X && iNextY == _pEndNode->Y ) {
                pJumpNode = s_PoolNode.Next();
                pJumpNode->Setup( iOffsetX, iNextY );

                return pJumpNode;
            }
        }
    } else if ( _iDirY != 0 ) {
        while (true) {
            // If next node is blocked, we can't jump
            if ( m_pGrid->IsBlocked( iNextX, iOffsetY + _iDirY ) ) {
                return NULL;
            }

            // If left/right node is blocked and _iDirY:left/_iDirY:right isn't, this is a jump node
            if ( ( !m_pGrid->IsBlocked( iNextX + 1, iOffsetY + _iDirY ) && m_pGrid->IsBlocked( iNextX + 1, iOffsetY ) ) ||
                ( !m_pGrid->IsBlocked( iNextX - 1, iOffsetY + _iDirY ) && m_pGrid->IsBlocked( iNextX - 1, iOffsetY ) ) ) {
                pJumpNode = s_PoolNode.Next();
                pJumpNode->Setup( iNextX, iOffsetY );

                return pJumpNode;
            }

            // Next node in Y direction
            iOffsetY += _iDirY;

            // If next node is the end, return it
            if ( iNextX == _pEndNode->X && iOffsetY == _pEndNode->Y ) {
                pJumpNode = s_PoolNode.Next();
                pJumpNode->Setup( iNextX, iOffsetY );

                return pJumpNode;
            }
        }
    }

    return Jump( iNextX, iNextY, _iDirX, _iDirY, _pStartNode, _pEndNode );
}

////////////////////////////////////////////////////////////
/// Find the interesting neighbours for the current node
////////////////////////////////////////////////////////////
void JumpPointSearch::FindInterestingNeighbours( Node* _pCurrentNode,
                                    std::vector< Node* >& _Neighbours ) {
    // If we are an only child, 
    // interesting neighbours are all neighbours around that aren't blocked
    Node* pNeighbourdNode;
    if ( !_pCurrentNode->parent ) {
        int iNeighbourX,
            iNeighbourY;
        for ( unsigned i = 0; i < 8; ++i ) {
            iNeighbourX = _pCurrentNode->X + s_Sides[i].x;
            iNeighbourY = _pCurrentNode->Y + s_Sides[i].y;

            if ( !m_pGrid->IsBlocked( iNeighbourX, iNeighbourY ) ) {
                pNeighbourdNode = s_PoolNode.Next();
                pNeighbourdNode->Setup( iNeighbourX, iNeighbourY );

                _Neighbours.push_back( pNeighbourdNode );
            }
        }
        return;
    }

    // In what direction are we ?
    int iDirX = tools::Clamp( _pCurrentNode->X - _pCurrentNode->parent->X, -1, 1 ),
        iDirY = tools::Clamp( _pCurrentNode->Y - _pCurrentNode->parent->Y, -1, 1 );

    // If we have a parent, 
    // interesting neighbours are neighbours following this pattern :
    //
    //  P : Parent
    //  N : Curent node
    //  O : Interesting neighbours
    //  X : Non-interesting neighbours
    //  B : Blocked node
    //  -> : Direction from parent to current node
    //
    //  STRAIGHT LINE
    //
    //  X       X       X
    //
    //  P    -> N       O
    //
    //  X       X       X
    //
    //  If there is a blocked node
    //
    //  X       X       X
    //
    //  P    -> N       O
    //
    //  X       B       O
    //
    //  DIAGONAL LINE
    //
    //  P --    X       X
    //      \
    //  X    -> N       O
    //
    //  X       O       O
    //
    //  If there is a blocked node
    //
    //  P --    X       X
    //      \
    //  B    -> N       O
    //
    //  O       O       O
    //
    if ( iDirX != 0 && iDirY != 0 ) {
        int iValidDiagonal = 0;
        if ( !m_pGrid->IsBlocked( _pCurrentNode->X + iDirX, _pCurrentNode->Y ) ) {
            // Node in iDirX is free
            pNeighbourdNode = s_PoolNode.Next();
            pNeighbourdNode->Setup( _pCurrentNode->X + iDirX, _pCurrentNode->Y );

            _Neighbours.push_back( pNeighbourdNode );
            ++iValidDiagonal;

            if ( !m_pGrid->IsBlocked( _pCurrentNode->X + iDirX, _pCurrentNode->Y - iDirY ) &&
                m_pGrid->IsBlocked( _pCurrentNode->X, _pCurrentNode->Y - iDirY ) ) {
                // Node in diagonal iDirX/-iDirY is free and Node in iDirY is blocked
                pNeighbourdNode = s_PoolNode.Next();
                pNeighbourdNode->Setup( _pCurrentNode->X + iDirX, _pCurrentNode->Y - iDirY );

                _Neighbours.push_back( pNeighbourdNode );
            }
        }
        if ( !m_pGrid->IsBlocked( _pCurrentNode->X, _pCurrentNode->Y + iDirY ) ) {
            // Node in iDirY is free
            pNeighbourdNode = s_PoolNode.Next();
            pNeighbourdNode->Setup( _pCurrentNode->X, _pCurrentNode->Y + iDirY );

            _Neighbours.push_back( pNeighbourdNode );
            ++iValidDiagonal;

            if ( !m_pGrid->IsBlocked( _pCurrentNode->X - iDirX, _pCurrentNode->Y + iDirY ) &&
                m_pGrid->IsBlocked( _pCurrentNode->X - iDirX, _pCurrentNode->Y ) ) {
                // Node in diagonal -iDirX/iDirY is free and Node in iDirX is blocked
                pNeighbourdNode = s_PoolNode.Next();
                pNeighbourdNode->Setup( _pCurrentNode->X - iDirX, _pCurrentNode->Y + iDirY );

                _Neighbours.push_back( pNeighbourdNode );
            }
        }
        if ( 0 < iValidDiagonal &&
            !m_pGrid->IsBlocked( _pCurrentNode->X + iDirX, _pCurrentNode->Y + iDirY )) {
            // Node in diagonal iDirX/iDirY is free
            pNeighbourdNode = s_PoolNode.Next();
            pNeighbourdNode->Setup( _pCurrentNode->X + iDirX, _pCurrentNode->Y + iDirY );

            _Neighbours.push_back( pNeighbourdNode );
        }
    } else if ( iDirX != 0 ) {
        if ( !m_pGrid->IsBlocked( _pCurrentNode->X + iDirX, _pCurrentNode->Y ) ) {
            // Node in same direction is free
            pNeighbourdNode = s_PoolNode.Next();
            pNeighbourdNode->Setup( _pCurrentNode->X + iDirX, _pCurrentNode->Y );

            _Neighbours.push_back( pNeighbourdNode );

            if ( !m_pGrid->IsBlocked( _pCurrentNode->X + iDirX, _pCurrentNode->Y - 1 ) &&
                m_pGrid->IsBlocked( _pCurrentNode->X, _pCurrentNode->Y - 1 ) ) {
                // Node in diagonal up/iDirX is free and up node is blocked
                pNeighbourdNode = s_PoolNode.Next();
                pNeighbourdNode->Setup( _pCurrentNode->X + iDirX, _pCurrentNode->Y - 1 );

                _Neighbours.push_back( pNeighbourdNode );
            }
            if ( !m_pGrid->IsBlocked( _pCurrentNode->X + iDirX, _pCurrentNode->Y + 1 ) &&
                m_pGrid->IsBlocked( _pCurrentNode->X, _pCurrentNode->Y + 1 ) ) {
                // Node in diagonal down/iDirX is free and down node is blocked
                pNeighbourdNode = s_PoolNode.Next();
                pNeighbourdNode->Setup( _pCurrentNode->X + iDirX, _pCurrentNode->Y + 1 );

                _Neighbours.push_back( pNeighbourdNode );
            }
        }
    } else { // iDirY != 0
        if ( !m_pGrid->IsBlocked( _pCurrentNode->X, _pCurrentNode->Y + iDirY ) ) {
            // Node in same direction is free
            pNeighbourdNode = s_PoolNode.Next();
            pNeighbourdNode->Setup( _pCurrentNode->X, _pCurrentNode->Y + iDirY );

            _Neighbours.push_back( pNeighbourdNode );

            if ( !m_pGrid->IsBlocked( _pCurrentNode->X - 1, _pCurrentNode->Y + iDirY ) &&
                m_pGrid->IsBlocked( _pCurrentNode->X - 1, _pCurrentNode->Y ) ) {
                // Node in diagonal iDirY/left is free and left node is blocked
                pNeighbourdNode = s_PoolNode.Next();
                pNeighbourdNode->Setup( _pCurrentNode->X - 1, _pCurrentNode->Y + iDirY );

                _Neighbours.push_back( pNeighbourdNode );
            }
            if ( !m_pGrid->IsBlocked( _pCurrentNode->X + 1, _pCurrentNode->Y + iDirY ) &&
                m_pGrid->IsBlocked( _pCurrentNode->X + 1, _pCurrentNode->Y ) ) {
                // Node in diagonal iDirY/right is free and right node is blocked
                pNeighbourdNode = s_PoolNode.Next();
                pNeighbourdNode->Setup( _pCurrentNode->X + 1, _pCurrentNode->Y + iDirY );

                _Neighbours.push_back( pNeighbourdNode );
            }
        }
    }
}

////////////////////////////////////////////////////////////
/// Find the possible successors for the current node
////////////////////////////////////////////////////////////
void JumpPointSearch::FindSuccessors( Node* _pCurrentNode, 
                                    Node* _pStartNode, 
                                    Node* _pEndNode,
                                    std::vector< Node* >& _Successors ) {
    std::vector< Node* > neighbours;
    FindInterestingNeighbours( _pCurrentNode, neighbours );

    int iDirX,
        iDirY;
    Node* pJumpNode;
    for ( unsigned i = 0; i < neighbours.size() ; ++i ) {
        // Direction from node to neigbour
        iDirX = tools::Clamp( neighbours[i]->X - _pCurrentNode->X, -1, 1 );
        iDirY = tools::Clamp( neighbours[i]->Y - _pCurrentNode->Y, -1, 1 );

        // Try to find a node to jump to
        pJumpNode = Jump( _pCurrentNode->X, _pCurrentNode->Y,
            iDirX, iDirY,
            _pStartNode, _pEndNode );

        // If we found a node, add it to the successors
        if ( pJumpNode ) {
            // If this is the end node, this is the only interesting successors
            if ( (*pJumpNode) == (*_pEndNode) ) {
                _Successors.clear();
                _Successors.push_back( pJumpNode );
                return;
            } else {
                _Successors.push_back( pJumpNode );
            }
        }
    }
}

#ifdef _DEBUG
bool JumpPointSearch::DebugStartPath( const sf::Vector2f& _Start, const sf::Vector2f& _End ) {
    s_PoolNode.ReleaseAll();
    ClearPath();

    sf::Vector2i startPoint,
        endPoint;
    Node* pStartNode = s_PoolNode.Next(),
        * pEndNode = s_PoolNode.Next();
    
    m_pGrid->ConvertToGridSpace( (int)_Start.x, (int)_Start.y, startPoint );
    m_pGrid->ConvertToGridSpace( (int)_End.x, (int)_End.y, endPoint );
    
    pStartNode->Setup( startPoint.x, startPoint.y );
    pEndNode->Setup( endPoint.x, endPoint.y );

    if ( m_pGrid->IsBlocked( endPoint.x, endPoint.y ) ) {
        return false;
    }

    pStartNode->F = Heuristic( startPoint, endPoint );

    m_DoneNodes.insert( td_PathMap::value_type( Node::UniqueKey( pStartNode, m_pGrid->GetNumCells() ), pStartNode ) );
    m_OpenSet.push_back( pStartNode );

    return true;
}

bool JumpPointSearch::DebugStepPath( const sf::Vector2f& _Start, const sf::Vector2f& _End ) {
    sf::Vector2i startPoint,
        endPoint,
        tmpPoint;
    Node* pCurrentNode = s_PoolNode.Next(),
        * pNeighbourNode,
        * pStartNode,
        * pEndNode = s_PoolNode.Next();
    std::vector< Node* > successors;
    unsigned uNumSuccessors,
        uNumCells = m_pGrid->GetNumCells();
    int iMapIdx;
    
    m_pGrid->ConvertToGridSpace( (int)_Start.x, (int)_Start.y, startPoint );
    m_pGrid->ConvertToGridSpace( (int)_End.x, (int)_End.y, endPoint );
    
    pCurrentNode->Setup( startPoint.x, startPoint.y );
    pStartNode = m_DoneNodes.find( Node::UniqueKey( pCurrentNode, uNumCells ) )->second;
    pEndNode->Setup( endPoint.x, endPoint.y );

    if ( m_ClosedMap.find( Node::UniqueKey( pEndNode, uNumCells ) ) != m_ClosedMap.end() ) {
        return true;
    }

    s_PoolNode.Release( pCurrentNode );

    td_PathPriorityQueue::iterator itLess,
        it;
    if ( !m_OpenSet.empty() ) {
        itLess = m_OpenSet.begin();
        for ( it = itLess + 1; it != m_OpenSet.end(); ++it ) {
            if ( **it < **itLess ) {
                itLess = it;
            }
        }
        pCurrentNode = *itLess;
        m_OpenSet.erase( itLess );

        m_ClosedMap.insert( td_PathMap::value_type( Node::UniqueKey( pCurrentNode, uNumCells ), pCurrentNode ) );

        // If this is the end node, reconstruct the path
        if ( (*pCurrentNode) == (*pEndNode) ) {
            ReconstructPath( pCurrentNode );
            return true;
        }

        // Find the successors
        successors.clear();
        FindSuccessors( pCurrentNode, pStartNode, pEndNode, successors );
        uNumSuccessors = successors.size();
        for ( unsigned i = 0; i < uNumSuccessors; ++i ) {
            pNeighbourNode  = successors[i];
            tmpPoint.x      = pNeighbourNode->X;
            tmpPoint.y      = pNeighbourNode->Y;
            iMapIdx         = Node::UniqueKey( pNeighbourNode, uNumCells );

            // If successor is in the closed map, continue
            if ( m_ClosedMap.count( iMapIdx ) != 0 ) {
                s_PoolNode.Release( pNeighbourNode );
                continue;
            }

            // Setup the neighbour
            pNeighbourNode->parent  = pCurrentNode;
            pNeighbourNode->G       = pCurrentNode->G + pNeighbourNode->DistanceTo( *pCurrentNode );

            // If this is the end node, reconstruct the path
            if ( (*pNeighbourNode) == (*pEndNode) ) {
                m_ClosedMap.insert( td_PathMap::value_type( Node::UniqueKey( pNeighbourNode, uNumCells ), pNeighbourNode ) );
                ReconstructPath( pNeighbourNode );
                return true;
            }

            // If this node has already been computed, reprioritize it, don't push it
            td_PathMap::iterator itNeighbour = m_DoneNodes.find( iMapIdx );

            if ( itNeighbour == m_DoneNodes.end() ) {
                pNeighbourNode->F = pNeighbourNode->G + Heuristic( tmpPoint, endPoint );

                // Add it to the open set
                m_DoneNodes.insert( td_PathMap::value_type( iMapIdx, pNeighbourNode ) );
                m_OpenSet.push_back( pNeighbourNode );
            } else if ( pNeighbourNode->G < itNeighbour->second->G ) {
                // Reprioritize existing node
                itNeighbour->second->parent = pNeighbourNode->parent;
                itNeighbour->second->G      = pNeighbourNode->G;
                itNeighbour->second->F      = pNeighbourNode->G + Heuristic( tmpPoint, endPoint );

                s_PoolNode.Release( pNeighbourNode );
            } else {
                s_PoolNode.Release( pNeighbourNode );
            }
        }
    }

    s_PoolNode.Release( pEndNode );
    return false;
}
#endif

    } // namespace ai
} // namespace smeg